/**
 * @file interfacesock.c
 * @brief Implementa le funzioni per realizzare un'interfaccia con dei socket.
 *
 * Vengono realizzare le funzioni per creare/avviare/chiudere una comunicazione
 * tramite socket aprendo delle porte sull'interfaccia locale.
 *
 * @date Mar 10, 2012
 * @author Francesco Corvino
 */
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <unistd.h> /* close() */
#include <string.h> /* memset() */

#include "interfacesock.h"

/**
 * @brief Setta opportumanente i valori e inizializza la comunicazione.
 *
 * Avvia la comunicazione creando il socket in ascolto sulla porta
 * fornita se è verso il client, altrimenti effettuando solo la connessione.
 *
 * @param iSock Oggetto da inizializzare.
 * @param pid Process Identificator con il quale aprire la sessione.
 * @param port Porta di ascolto del socket server.
 * @param type Specifica se è un socket server o client
 */
void InterfaceSock_init(InterfaceSock * iSock,
		pid_t pid, unsigned int port ,unsigned int type){
	/// - Assegna i valori alla struttura
	iSock->pid = pid;
	iSock->port = port;
	iSock->type = type;
	/// - Per default indirizzo 127.0.0.1
	struct sockaddr_in servAddr;
	servAddr.sin_family = AF_INET;
	servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
	/// - Carica la porta del server
	servAddr.sin_port = htons(port);

	/// - create socket
	if((iSock->sd = socket(AF_INET, SOCK_DGRAM, 0))<0) {
		iSock->sd = -1;
		return;
	}

	/// - bind to localhost,port
	if(type & SOCKSESSION_SERVER){
		if(bind(iSock->sd, (struct sockaddr *) &servAddr, sizeof(servAddr))<0) {
			close(iSock->sd);
			iSock->sd = -1;
			return;
		}
	}
	/// - connect to localhost,port
	else {
		if(connect(iSock->sd, (struct sockaddr *) &servAddr, sizeof(servAddr))<0) {
			close(iSock->sd);
			iSock->sd = -1;
			return;
		}
	}

	/// - ricava le informazioni sulla porta aperta se non specificata
	if(port==0){
		if(getsockname(iSock->sd,&(iSock->buffAddr),&(iSock->lenBuffAddr))<0){
			perror("getsockname");
		} else {
			iSock->port = htons(((struct sockaddr_in *) &(iSock->buffAddr))->sin_port);
		}
	}
}

/**
 * @brief Termina una comunicazione chiudendo il socket.
 *
 * @param iSock Oggetto contenente i dati sui canali.
 */
void InterfaceSock_close(InterfaceSock * iSock){
    if(iSock->sd>0) close(iSock->sd);
}

/**
 * @brief Metodo delegato della funzione read().
 *
 * @param fifo puntatore della difo da cui leggere.
 * @param buffer puntatore dove allocare i bytes letti.
 * @param maxdim dimensione massima della memoria da allocare.
 * @return numero di byte effettivamente letti.
 */
int InterfaceSock_rcvMsg(InterfaceSock * iSock,char * buffer,int maxdim){
	if(iSock->type & SOCKSESSION_SERVER){
		return recvfrom(iSock->sd,buffer,maxdim,0,
				&(iSock->buffAddr),&(iSock->lenBuffAddr));
	}
	return read(iSock->sd,buffer,maxdim);
}

/**
 * @brief Metodo delegato della funzione write().
 *
 * @param fifo puntatore della fifo su cui scrivere.
 * @param buffer puntatore alla stringa da scrivere.
 * @return numero di byte effettivamente scritti.
 */
int InterfaceSock_sendMsg(InterfaceSock * iSock,char * buffer){
	if(iSock->type & SOCKSESSION_SERVER){
		return sendto(iSock->sd, buffer, strlen(buffer),0,
				&(iSock->buffAddr),iSock->lenBuffAddr);
	}
	return write(iSock->sd,buffer,strlen(buffer));
}

/**
 * @brief Funzione che permette di registrare una sessione.
 *
 * @param iSock oggetto da registrare.
 * @param port
 */
void InterfaceSock_register(int port){
	char registrationMsg[255];
	pid_t mypid = getpid();
	sprintf(registrationMsg,"%s %d %d ", NEWSESSION_STR, mypid, port);

	InterfaceSock regSock;
	/// - inizializza il canale per la registrazione
	InterfaceSock_init(&regSock, mypid,
			SOCKSESSION_MAINPORT,SOCKSESSION_CLIENT);
	/// - invia il messaggio di registrazione
	InterfaceSock_sendMsg(&regSock,registrationMsg);
	/// - chiude il canale di registrazione
	InterfaceSock_close(&regSock);
}

/**
 * @brief Gestisce l'attesa di registazione
 * @param listener interfaccia socket di ascolto
 * @param newComm interfaccia creata per la nuova sessione
 */
void InterfaceSock_waitReg(InterfaceSock * listener, InterfaceSock* newComm){
	pid_t pid;
	unsigned int port;
	char registrationMsg[255];
	/// - Si mette in attessa dell'arrivo di un messaggio
	InterfaceSock_rcvMsg(listener,registrationMsg,255);
	/// - Scarta il testo iniziale
	char *tmp = registrationMsg + strlen(NEWSESSION_STR);
	/// - Legge i dati
	sscanf(tmp," %d %d ",&pid, &port);
	/// - Inizializza i socket per comunicare
	InterfaceSock_init(newComm,pid,port,SOCKSESSION_CLIENT);
	/// - Comunica che è pronto
	sprintf(registrationMsg,"%s %d OK ",NEWSESSION_STR,listener->pid);
	InterfaceSock_sendMsg(listener,registrationMsg);
}
